package com.hmdp.utils;

import cn.hutool.core.lang.Tuple;
import com.hmdp.common.Constants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Component
@RequiredArgsConstructor
@Slf4j
public class GeoUtils {
    private final StringRedisTemplate stringRedisTemplate;

    public Distance getDistance(String key, String member1, String member2, Metrics metric) {
        GeoOperations<String, String> geoOperations = stringRedisTemplate.opsForGeo();
        return geoOperations.distance(key, member1, member2, metric);
    }

    public void addLocation(String key, Double x, Double y, String member) {
        GeoOperations<String, String> geoOperations = stringRedisTemplate.opsForGeo();
        Long result = geoOperations.add(key, new Point(x, y), member);
        if (result != null && result > 0) {
            log.info("{}条记录的位置加入到了redis中", result);
        }
        else {
            log.error("{}的位置信息加入失败", key + member);
        }
    }

    public void addLocation(String key, Double x, Double y, String member, Long timeout, TimeUnit timeUnit) {
        GeoOperations<String, String> geoOperations = stringRedisTemplate.opsForGeo();
        Long result = geoOperations.add(key, new Point(x, y), member);
        stringRedisTemplate.expire(key, timeout, timeUnit);
        if (result != null && result > 0) {
            log.info("{}条记录的位置加入到了redis中", result);
        }
        else {
            log.error("{}的位置信息加入失败", key + member);
        }
    }

    public Tuple getNearLocation(String key, Double x, Double y, Double distance, Metrics metric, Long limit) {
        GeoOperations<String, String> geoOperations = stringRedisTemplate.opsForGeo();
        Circle circle = new Circle(new Point(x, y), new Distance(distance, metric));
        RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs = RedisGeoCommands.GeoRadiusCommandArgs
                                                                                     .newGeoRadiusArgs()
                                                                                     .includeDistance()
                                                                                     .sortAscending()
                                                                                     .limit(limit);
        GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = geoOperations.radius(key, circle, geoRadiusCommandArgs);
        if (geoResults == null) {
            return null;
        }

        ArrayList<Long> idList = new ArrayList<>();
        HashMap<Long, Distance> idToDistance = new HashMap<>();
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> geoResultsContent = geoResults.getContent();
        geoResultsContent.forEach(geoResult -> {
            String idStr = geoResult.getContent().getName();
            Long id = Long.parseLong(idStr);
            idList.add(id);
            idToDistance.put(id, geoResult.getDistance());
        });

        return new Tuple(idList, idToDistance);
    }

    public Tuple getNearLocation(String key, Double x, Double y, Double distance, Long limit) {
        GeoOperations<String, String> geoOperations = stringRedisTemplate.opsForGeo();
        Circle circle = new Circle(new Point(x, y), new Distance(distance));
        RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs = RedisGeoCommands.GeoRadiusCommandArgs
                                                                                     .newGeoRadiusArgs()
                                                                                     .includeDistance()
                                                                                     .sortAscending()
                                                                                     .limit(limit);
        GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = geoOperations.radius(key, circle, geoRadiusCommandArgs);
        if (geoResults == null) {
            return null;
        }

        ArrayList<Long> idList = new ArrayList<>();
        HashMap<Long, Distance> idToDistance = new HashMap<>();
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> geoResultsContent = geoResults.getContent();
        geoResultsContent.forEach(geoResult -> {
            String idStr = geoResult.getContent().getName();
            Long id = Long.parseLong(idStr);
            idList.add(id);
            idToDistance.put(id, geoResult.getDistance());
        });

        return new Tuple(idList, idToDistance);
    }

    public Tuple getNearLocation(String key, Double x, Double y, Long limit) {
        GeoOperations<String, String> geoOperations = stringRedisTemplate.opsForGeo();
        Circle circle = new Circle(new Point(x, y), new Distance(Double.MAX_VALUE));
        RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs = RedisGeoCommands.GeoRadiusCommandArgs
                                                                                     .newGeoRadiusArgs()
                                                                                     .includeDistance()
                                                                                     .sortAscending()
                                                                                     .limit(limit);
        GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = geoOperations.radius(key, circle, geoRadiusCommandArgs);
        if (geoResults == null) {
            return null;
        }

        ArrayList<Long> idList = new ArrayList<>();
        HashMap<Long, Distance> idToDistance = new HashMap<>();
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> geoResultsContent = geoResults.getContent();
        geoResultsContent.forEach(geoResult -> {
            String idStr = geoResult.getContent().getName();
            Long id = Long.parseLong(idStr);
            idList.add(id);
            idToDistance.put(id, geoResult.getDistance());
        });

        return new Tuple(idList, idToDistance);
    }

    public Tuple getNearLocation(String key, Double x, Double y) {
        GeoOperations<String, String> geoOperations = stringRedisTemplate.opsForGeo();
        Circle circle = new Circle(new Point(x, y), new Distance(Double.MAX_VALUE));
        RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs = RedisGeoCommands.GeoRadiusCommandArgs
                                                                                     .newGeoRadiusArgs()
                                                                                     .includeDistance()
                                                                                     .sortAscending()
                                                                                     .limit(Constants.RADIUS_DISPLAY_LIMIT);
        GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = geoOperations.radius(key, circle, geoRadiusCommandArgs);
        if (geoResults == null) {
            return null;
        }

        ArrayList<Long> idList = new ArrayList<>();
        ArrayList<Double> disList = new ArrayList<>();
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> geoResultsContent = geoResults.getContent();
        geoResultsContent.forEach(geoResult -> {
            String idStr = geoResult.getContent().getName();
            Long id = Long.parseLong(idStr);
            idList.add(id);
            disList.add(geoResult.getDistance().getValue());
        });

        return new Tuple(idList, disList);
    }
}
